import datetime

import numpy as np
import pandas as pd

from aileakage.PipeStatic import PipeStatic


class Pipe:

    def __init__(self, id=None, diameter=None, material=None, pressure=None, installation_year=None,
                 pressure_score=None,
                 time=None, flow=None, demand=None):
        self.id = id
        self.diameter = diameter
        self.material = material
        self.pressure = pressure
        self.installation_year = installation_year
        self.pressure_score = pressure_score
        self.diameter_score = 0
        self.material_score = 0
        self.pressure_score = self.pressure_score if pressure_score is not None else 0
        self.risk_score = 0
        self.risk_level = "low"
        self.age = 0
        self.pressure_coef = 0
        self.effective_age = 0
        self.age_risk = 0
        self.years_until_critical = None
        for i in range(1, 11):
            setattr(self, f'predicted_risk_{i}y', 0)
        self.time = time
        self.flow = flow
        self.demand = demand
        self.pressure_diff = 0
        self.flow_diff = 0
        self.pressure_change_rate = 0
        self.flow_change_rate = 0
        self.is_burst = False
        self.burst_type = None

    # 风险等级计算
    @classmethod
    def risk_level_cal(cls, pipes):
        """
        风险等级计算
        :param pipes: 待处理数据
        """
        # Weights for each factor
        w_diameter = 0.3
        w_material = 0.3
        w_pressure = 0.4

        # 计算平均直径用于参考
        diameters = [pipe.diameter for pipe in pipes]
        DN_mean = np.mean(diameters)
        for pipe in pipes:
            try:
                # 使用sigmoid函数计算直径分数
                pipe.diameter_score = 2 / (1 + np.exp(-np.log(DN_mean / pipe.diameter)))
                # 映射材料分数
                pipe.material_score = PipeStatic.material_scores.get(pipe.material, 0.75)
            except KeyError:
                print(f"Warning: Material {pipe.material} not found in material_scores. Using default score of 0.")
                pipe.material_score = 0

        # 使用sigmoid函数计算压力分数
        pressures = [pipe.pressure for pipe in pipes]
        P_mean = np.mean(pressures)
        for pipe in pipes:
            pipe.pressure_score = 2 / (1 + np.exp(-np.log(pipe.pressure / P_mean)))

        # 计算总风险分数
        for pipe in pipes:
            pipe.risk_score = (w_diameter * pipe.diameter_score +
                               w_material * pipe.material_score +
                               w_pressure * pipe.pressure_score)

        # 根据百分位数分配风险等级
        risk_scores = [pipe.risk_score for pipe in pipes]
        q65 = np.percentile(risk_scores, 65)
        q85 = np.percentile(risk_scores, 85)
        for pipe in pipes:
            if pipe.risk_score >= q85:
                pipe.risk_level = PipeStatic.risk_lv_map['high']
            elif pipe.risk_score >= q65:
                pipe.risk_level = PipeStatic.risk_lv_map['medium']
            else:
                pipe.risk_level = PipeStatic.risk_lv_map['low']

        # 封装返回结果
        result = []
        for pipe in pipes:
            result.append({
                "id": pipe.id,
                # "diameter": pipe.diameter,
                # "material": pipe.material,
                # "pressure": pipe.pressure,
                # "risk_score": pipe.risk_score,
                "pressure_score": round(pipe.pressure_score, 9),
                "riskLevel": pipe.risk_level
            })
        return result

    # 基于年龄的风险评估
    @classmethod
    def age_based_risk(cls, pipes):
        """
        基于年龄的风险评估
        :param pipes: 待处理数据
        """
        current_year = datetime.datetime.now().year
        current_month = datetime.datetime.now().month
        for pipe in pipes:
            if pipe.installation_year is not None:
                try:
                    # 尝试将 installation_year 转换为年份
                    if isinstance(pipe.installation_year, (datetime.datetime, datetime.date)):
                        installation_year = pipe.installation_year.year
                        installation_month = pipe.installation_year.month
                    elif isinstance(pipe.installation_year, int):
                        # 如果只传了年份
                        installation_year = pipe.installation_year
                        installation_month = 0
                    elif isinstance(pipe.installation_year, str):
                        try:
                            # 尝试解析日期字符串
                            date_obj = datetime.datetime.strptime(pipe.installation_year, '%Y-%m-%d')
                            installation_year = date_obj.year
                            installation_month = date_obj.month
                        except ValueError:
                            print(f"无法解析日期字符串: {pipe.installation_year}")
                            continue
                    else:
                        print(f"不支持的日期类型: {type(pipe.installation_year)}")
                        continue
                    # 精确计算年龄
                    age_in_months = (current_year - installation_year) * 12 + (current_month - installation_month)
                    pipe.age = round(age_in_months / 12, 2)
                    # Calculate pressure coefficient (0.8 - 1.2 range based on pressure score)
                    pipe.pressure_coef = 0.8 + 0.4 * pipe.pressure_score

                    # Calculate effective age (considering pressure influence)
                    pipe.effective_age = pipe.age * pipe.pressure_coef
                except Exception as e:
                    print(f"处理管道时出错: {e}")

        # Calculate risk based on age
        def calculate_age_risk(pipe):
            base_life = PipeStatic.material_life.get(pipe.material, 30)  # default 30 if material not found
            threshold = base_life * 0.7  # 70% of expected life as threshold
            x = pipe.effective_age / threshold
            return (1 / (1 + np.exp(-4 * np.log(x)))) * 100 if x > 0 else 0

        for pipe in pipes:
            if pipe.installation_year is not None:
                pipe.age_risk = calculate_age_risk(pipe)

        # Calculate predicted risk for next 10 years
        for pipe in pipes:
            if pipe.installation_year is not None:
                age_risk_line = {}  # 为每个管道创建独立的 age_risk_line 字典
                for i in range(1, 11):
                    future_pipe = Pipe(pipe.id, pipe.diameter, pipe.material, pipe.pressure, pipe.installation_year,
                                       pipe.pressure_score)
                    future_pipe.effective_age = pipe.effective_age + i * pipe.pressure_coef
                    target_year = current_year + i
                    age_risk_line[target_year] = round(calculate_age_risk(future_pipe), 2)
                pipe.age_risk_line = age_risk_line  # 将计算好的 age_risk_line 赋值给当前管道对象

        # Calculate critical year (when risk exceeds 0.8)
        def find_critical_year(pipe):
            age = pipe.effective_age
            base_life = PipeStatic.material_life.get(pipe.material, 30)
            year = 0
            while year < 50:  # limit to 50 years in future
                future_pipe = Pipe(pipe.id, pipe.diameter, pipe.material, pipe.pressure,
                                   pipe.installation_year, pipe.pressure_score)
                future_pipe.effective_age = age + year * pipe.pressure_coef
                risk = calculate_age_risk(future_pipe)
                if risk > 80:
                    return year
                year += 1
            return None

        for pipe in pipes:
            if pipe.installation_year is not None:
                pipe.years_until_critical = find_critical_year(pipe)

        result = []
        for pipe in pipes:
            pipe_info = {
                "id": pipe.id,
                # "material": pipe.material,
                # "installation_year": pipe.installation_year,
                # "pressure_score": round(pipe.pressure_score, 3),
                "pipeAge": pipe.age,
                # "pressure_coef": round(pipe.pressure_coef, 3),
                # "effective_age": round(pipe.effective_age, 2),
                # "age_risk": round(pipe.age_risk, 3),
                "years_until_critical": pipe.years_until_critical,
                "ageRiskLineMap": pipe.age_risk_line
            }
            result.append(pipe_info)
        return result

    # 智能调压
    @classmethod
    def smart_pressure_control(cls, pipes):
        """
        智能调压
        :param pipes: 待处理数据
        """
        # 过滤掉 pressure 为 None 的 Pipe 对象
        valid_pipes = [pipe for pipe in pipes if pipe.pressure is not None and pipe.time is not None]
        if not valid_pipes:
            return []

        # 创建 DataFrame
        data = {
            "id": [pipe.id for pipe in valid_pipes],
            "pressure": [pipe.pressure for pipe in valid_pipes],
            "time": [pipe.time for pipe in valid_pipes]
        }
        df = pd.DataFrame(data)

        # 转换时间格式并提取小时
        df['time'] = pd.to_datetime(df['time'])
        df['hour'] = df['time'].dt.hour

        # 定义时间周期和压力降低系数
        def get_reduction_factor(hour):
            if hour in range(7, 10) or hour in range(10, 14) or hour in range(17, 23):
                return 1.0  # 高峰时段不降低压力
            elif hour in range(0, 6):
                return 0.7  # 凌晨时段降低 30%
            else:
                return 0.85  # 其他时段降低 15%

        # 应用压力调整
        df['reduction_factor'] = df['hour'].apply(get_reduction_factor)
        df['adjusted_pressure'] = df['pressure'] * df['reduction_factor']

        # 确保最小压力为 0.25 MPa
        df['adjusted_pressure'] = df['adjusted_pressure'].clip(lower=0.25)

        # 更新 Pipe 对象的压力值
        id_to_pipe = {pipe.id: pipe for pipe in pipes}
        for index, row in df.iterrows():
            pipe = id_to_pipe[row['id']]
            pipe.pressure = row['adjusted_pressure']

        # 构建结果列表
        result = []
        for pipe in pipes:
            result.append({
                "id": pipe.id,
                "time": pipe.time,
                "pressure": pipe.pressure
            })

        return result

    # 爆管预测
    @classmethod
    def detect_pipe_burst(cls, pipes, pressure_threshold=0.05, flow_threshold=0.10, closed_flow_threshold=0.10):
        """
        爆管预测

        ps: 这里最低数据量是2行数据，但是水力模型不可能一致模拟（需水量需要人工填写），
        这个更适合针对现场具有流量监测的节点（甚至流量监测-压力），需水量也只能给到定值
        :param pipes: 待处理数据
        :param pressure_threshold: 压力界限，默认：0.05
        :param flow_threshold: 流量界限，默认：0.1
        :param closed_flow_threshold: 调节流量（减小）界限，默认：0.1
        """
        # 按照时间排序
        pipes.sort(key=lambda x: x.time)

        for i in range(1, len(pipes)):
            # 计算压力和流量的差值
            pipes[i].pressure_diff = pipes[i].pressure - pipes[i - 1].pressure
            pipes[i].flow_diff = pipes[i].flow - pipes[i - 1].flow

            # 计算百分比变化
            pipes[i].pressure_change_pct = abs(pipes[i].pressure_diff / pipes[i - 1].pressure) if pipes[
                                                                                                      i - 1].pressure != 0 else 0
            pipes[i].flow_change_pct = abs(pipes[i].flow_diff / pipes[i - 1].flow) if pipes[i - 1].flow != 0 else 0

            # 判断是否为封闭段
            pipes[i].is_closed = pipes[i].demand == 0

            # 初始化爆管检测结果
            pipes[i].is_burst = False
            pipes[i].burst_type = None

            # 检测封闭段的爆管情况
            closed_burst = (
                    pipes[i].is_closed and
                    (pipes[i].flow_change_pct > closed_flow_threshold)
            )

            # 检测开放段的爆管情况
            open_burst = (
                    not pipes[i].is_closed and
                    (pipes[i].pressure_change_pct > pressure_threshold) and
                    (pipes[i].flow_change_pct > flow_threshold) and
                    (pipes[i].pressure_diff < 0) and  # 压力必须下降
                    (pipes[i].flow_diff > 0)  # 流量必须增加
            )

            # 设置爆管检测结果
            if closed_burst:
                pipes[i].is_burst = True
                pipes[i].burst_type = 'closed_segment'
            elif open_burst:
                pipes[i].is_burst = True
                pipes[i].burst_type = 'pressure_flow'

        result = []
        for pipe in pipes:
            if pipe.is_burst:
                pipe_info = {
                    "id": pipe.time,
                    "time": pipe.time,
                    "pressure": round(pipe.pressure, 3),
                    "flow": round(pipe.flow, 3),
                    "demand": round(pipe.demand, 3),
                    "is_burst": pipe.is_burst,
                    "burst_type": pipe.burst_type
                }
                result.append(pipe_info)
        return result

    # 压力瞬变报警
    @classmethod
    def detect_pressure_anomaly(cls, pipes, threshold=0.20, window_days=30):
        """
        压力瞬变报警
        :param pipes: 待处理数据
        :param threshold: 异常界限，默认：0.2
        :param window_days: 时间间隔（天），默认：30
        """
        # 过滤掉 time 或 pressure 为 None 的管道
        valid_pipes = [pipe for pipe in pipes if pipe.time is not None and pipe.pressure is not None]
        if not valid_pipes:
            return pipes

        # 创建 DataFrame
        data = {
            "time": [pipe.time for pipe in valid_pipes],
            "pressure": [pipe.pressure for pipe in valid_pipes]
        }
        df_monitor = pd.DataFrame(data)

        # Convert time to datetime if it's not already
        df_monitor['time'] = pd.to_datetime(df_monitor['time'])

        # Create time block (0 - 47 for 48 half - hour periods in a day)
        df_monitor['time_block'] = (df_monitor['time'].dt.hour * 2 +
                                    (df_monitor['time'].dt.minute >= 30).astype(int))

        # Calculate baseline pressures for each time block
        historical_data = df_monitor[
            df_monitor['time'] >= df_monitor['time'].max() - pd.Timedelta(days=window_days)
            ]

        pressure_baselines = historical_data.groupby('time_block')['pressure'].mean()

        # Add baseline pressures to main dataframe
        df_monitor['pressure_baseline'] = df_monitor['time_block'].map(pressure_baselines)

        # Calculate deviation from baseline
        df_monitor['pressure_deviation'] = ((df_monitor['pressure'] - df_monitor['pressure_baseline']) /
                                            df_monitor['pressure_baseline']).abs()

        # Detect anomalies
        df_monitor['anomaly_detected'] = df_monitor['pressure_deviation'] > threshold

        # 将计算结果更新到原始的 Pipe 对象列表中
        # id_to_pipe = {pipe.id: pipe for pipe in pipes}
        # for index, row in df_monitor.iterrows():
        #     if row['anomaly_detected']:
        #         pipe = id_to_pipe[row['id']]
        #         pipe.pressure_baseline = row['pressure_baseline']
        #         pipe.pressure_deviation = row['pressure_deviation']
        #         pipe.anomaly_detected = row['anomaly_detected']
        #         pipe.time_block = row['time_block']
        # return pipes
        # 找出最早的异常数据
        anomaly_data = df_monitor[df_monitor['anomaly_detected']]
        if not anomaly_data.empty:
            earliest_anomaly = anomaly_data[anomaly_data['time'] == anomaly_data['time'].min()].iloc[0]
            # 将计算结果更新到原始的 Pipe 对象列表中
            id_to_pipe = {str(pipe.time): pipe for pipe in pipes}
            pipe = id_to_pipe[str(earliest_anomaly['time'])]
            pipe.pressure_baseline = earliest_anomaly['pressure_baseline']
            pipe.pressure_deviation = earliest_anomaly['pressure_deviation']
            pipe.anomaly_detected = earliest_anomaly['anomaly_detected']
            pipe.time_block = earliest_anomaly['time_block']
            # 数据封装
            result_dict = {
                'time': str(pipe.time),
                'pressure': round(pipe.pressure, 3),
                'pressure_baseline': round(float(pipe.pressure_baseline), 3),  # 转换为 Python 内置 float 类型
                'pressure_deviation': round(float(pipe.pressure_deviation), 3),  # 转换为 Python 内置 float 类型
                'anomaly_detected': bool(pipe.anomaly_detected),  # 转换为 Python 内置 bool 类型
                'time_block': int(pipe.time_block)  # 转换为 Python 内置 int 类型
            }
            return result_dict
        return None
